package com.bakerj.infinitecards;

import com.bakerj.infinitecards.transformer.AttrUtils;

import ohos.agp.components.AttrSet;
import ohos.agp.components.BaseItemProvider;
import ohos.agp.components.Component;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.ComponentContainer;
import ohos.agp.database.DataSetSubscriber;
import ohos.app.Context;

/**
 * InfiniteCardView
 *
 * @author BakerJ
 * @since : 2021.5.24
 */
public class InfiniteCardView extends ComponentContainer
        implements ComponentContainer.ArrangeListener, Component.EstimateSizeListener {
    /**
     * Three types of animation
     * ANIM_TYPE_FRONT:custom animation for chosen card, common animation for other cards
     */
    public static final int ANIM_TYPE_FRONT = 0;

    /**
     * Three types of animation
     * ANIM_TYPE_SWITCH:switch the position by custom animation of the first card and the chosen card
     */
    public static final int ANIM_TYPE_SWITCH = 1;

    /**
     * Three types of animation
     * ANIM_TYPE_FRONT_TO_LAST:moving the first card to last position by custom animation, common animation for others
     */
    public static final int ANIM_TYPE_FRONT_TO_LAST = 2;

    // cardHeight / cardWidth
    private static final float CARD_SIZE_RATIO = 0.5f;

    // cardHeight / cardWidth
    private float mCardRatio = CARD_SIZE_RATIO;

    // cardHeight / cardWidth
    private int mCardWidth;

    // cardHeight / cardWidth
    private int mCardHeight;

    // animation helper
    private CardAnimationHelper mAnimationHelper;

    // view adapter
    private BaseItemProvider mAdapter;

    /**
     * InfiniteCardView
     *
     * @param context context
     * @param attrSet attrSet
     */
    public InfiniteCardView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(context, attrSet);
        setClickable(true);
    }

    private void init(Context context, AttrSet attrSet) {
        int animType = ANIM_TYPE_FRONT;
        int animDuration = CardAnimationHelper.ANIM_DURATION;
        int animAddRemoveDuration = CardAnimationHelper.ANIM_ADD_REMOVE_DURATION;
        int animAddRemoveDelay = CardAnimationHelper.ANIM_ADD_REMOVE_DELAY;
        if (attrSet != null) {
            animType = AttrUtils.getInteger(attrSet, "animType", ANIM_TYPE_FRONT);
            mCardRatio = AttrUtils.getFloat(attrSet, "mCardRatio", CARD_SIZE_RATIO);
            animDuration = AttrUtils.getInteger(attrSet, "animDuration", 1000);
            animAddRemoveDuration = AttrUtils
                    .getInteger(attrSet, "animAddRemoveDuration", CardAnimationHelper.ANIM_ADD_REMOVE_DURATION);
            animAddRemoveDelay = AttrUtils
                    .getInteger(attrSet, "animAddRemoveDelay", CardAnimationHelper.ANIM_ADD_REMOVE_DELAY);
        }
        mAnimationHelper = new CardAnimationHelper(animType, animDuration, this);
        mAnimationHelper.setAnimAddRemoveDuration(animAddRemoveDuration);
        mAnimationHelper.setAnimAddRemoveDelay(animAddRemoveDelay);
        setArrangeListener(this);
        setEstimateSizeListener(this);
    }

    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        int childCount = getChildCount();
        int childWidth;
        int childHeight;
        int childLeft;
        int childTop;
        int childRight;
        int childBottom;
        int width = getWidth();
        int height = getHeight();
        for (int j = 0; j < childCount; j++) {
            Component childView = getComponentAt(j);
            childWidth = childView.getWidth();
            childHeight = childView.getEstimatedHeight();
            childLeft = (width - childWidth) / 2;
            childTop = (height - childHeight) / 2;
            childRight = childLeft + childWidth;
            childBottom = childTop + childHeight;
            childView.arrange(childLeft, 500, childRight, childBottom);
        }
        return true;
    }

    /**
     * addCardView
     *
     * @param card card
     */
    void addCardView(CardItem card) {
        addComponent(getCardView(card));
    }

    /**
     * addCardView
     *
     * @param card     card
     * @param position position
     */
    void addCardView(CardItem card, int position) {
        addComponent(getCardView(card), position);
    }

    private Component getCardView(final CardItem card) {
        Component view = card.view;
        DependentLayout.LayoutConfig layoutParams = new DependentLayout.LayoutConfig(mCardWidth, mCardHeight);
        view.setLayoutConfig(layoutParams);
        view.setClickedListener(component -> bringCardToFront(card));
        return view;
    }


    @Override
    public boolean onEstimateSize(int i1, int i2) {
        int widthMode = EstimateSpec.getMode(i1);
        int heightMode = EstimateSpec.getMode(i2);
        int sizeWidth = EstimateSpec.getSize(i1);
        int sizeHeight = EstimateSpec.getSize(i2);
        measureChildren(i1, i2);
        if (widthMode != EstimateSpec.PRECISE || heightMode != EstimateSpec.PRECISE) {
            int childCount = getChildCount();
            int childWidth = 0;
            int childHeight = 0;
            for (int j = 0; j < childCount; j++) {
                Component childView = getComponentAt(j);
                childWidth = Math.max(childView.getEstimatedWidth(), childWidth);
                childHeight = Math.max(childView.getEstimatedHeight(), childHeight);
            }
            setEstimatedSize((widthMode == EstimateSpec.PRECISE) ? sizeWidth : childWidth,
                    (heightMode == EstimateSpec.PRECISE) ? sizeHeight : childHeight);
        } else {
            setEstimatedSize(sizeWidth, sizeHeight);
        }
        if (mCardWidth == 0 || mCardHeight == 0) {
            setCardSize(true);
        }
        return true;
    }

    private void measureChildren(int widthEstimatedConfig, int heightEstimatedConfig) {
        for (int idx = 0; idx < getChildCount(); idx++) {
            Component childView = getComponentAt(idx);
            if (childView != null) {
                measureChild(childView, widthEstimatedConfig, heightEstimatedConfig);
            }
        }
    }

    private void measureChild(Component child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
        LayoutConfig lc = child.getLayoutConfig();
        int childWidthMeasureSpec = EstimateSpec.getChildSizeWithMode(
                lc.width, parentWidthMeasureSpec, EstimateSpec.UNCONSTRAINT);
        int childHeightMeasureSpec = EstimateSpec.getChildSizeWithMode(
                lc.height, parentHeightMeasureSpec, EstimateSpec.UNCONSTRAINT);
        child.estimateSize(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    private void setCardSize(boolean resetAdapter) {
        mCardWidth = getEstimatedWidth();
        mCardHeight = (int) (mCardWidth * mCardRatio);
        mAnimationHelper.setCardSize(mCardWidth, mCardHeight);
        mAnimationHelper.initAdapterView(mAdapter, resetAdapter);
    }

    private void bringCardToFront(CardItem card) {
        if (!isClickable()) {
            return;
        }
        mAnimationHelper.bringCardToFront(card);
    }

    /**
     * bring the specific position card to front
     *
     * @param position position
     */
    public void bringCardToFront(int position) {
        mAnimationHelper.bringCardToFront(position);
    }

    /**
     * set view adapter
     *
     * @param adapter adapter
     */
    public void setAdapter(BaseItemProvider adapter) {
        this.mAdapter = adapter;
        mAdapter.addDataSubscriber(new DataSetSubscriber() {
            @Override
            public void onChanged() {
                mAnimationHelper.notifyDataSetChanged(mAdapter);
            }
        });
        mAnimationHelper.initAdapterView(adapter, true);
    }

    /**
     * set Transformer To Front
     *
     * @param toFrontTransformer toFrontTransformer
     */
    public void setTransformerToFront(AnimationTransformer toFrontTransformer) {
        mAnimationHelper.setTransformerToFront(toFrontTransformer);
    }

    /**
     * set Transformer To Back
     *
     * @param toBackTransformer toBackTransformer
     */
    public void setTransformerToBack(AnimationTransformer toBackTransformer) {
        mAnimationHelper.setTransformerToBack(toBackTransformer);
    }

    /**
     * set Common Switch Transformer
     *
     * @param commonTransformer commonTransformer
     */
    public void setCommonSwitchTransformer(AnimationTransformer commonTransformer) {
        mAnimationHelper.setCommonSwitchTransformer(commonTransformer);
    }

    /**
     * set Transformer Common
     *
     * @param transformerCommon transformerCommon
     */
    public void setTransformerCommon(AnimationTransformer transformerCommon) {
        mAnimationHelper.setTransformerCommon(transformerCommon);
    }

    /**
     * set ZIndex Transformer To Front
     *
     * @param zIndexTransformerToFront zIndexTransformerToFront
     */
    public void setZIndexTransformerToFront(ZIndexTransformer zIndexTransformerToFront) {
        mAnimationHelper.setZIndexTransformerToFront(zIndexTransformerToFront);
    }

    /**
     * set ZIndex Transformer To Back
     *
     * @param zIndexTransformerToBack zIndexTransformerToBack
     */
    public void setZIndexTransformerToBack(ZIndexTransformer zIndexTransformerToBack) {
        mAnimationHelper.setZIndexTransformerToBack(zIndexTransformerToBack);
    }

    /**
     * set ZIndex Transformer Common
     *
     * @param zIndexTransformerCommon zIndexTransformerCommon
     */
    public void setZIndexTransformerCommon(ZIndexTransformer zIndexTransformerCommon) {
        mAnimationHelper.setZIndexTransformerCommon(zIndexTransformerCommon);
    }

    /**
     * set Anim Interpolator
     *
     * @param animInterpolator animInterpolator
     */
    public void setAnimInterpolator(Interpolator animInterpolator) {
        mAnimationHelper.setAnimInterpolator(animInterpolator);
    }

    /**
     * set Anim Type
     *
     * @param animType animType
     */
    public void setAnimType(int animType) {
        mAnimationHelper.setAnimType(animType);
    }

    /**
     * set Transformer Anim Add
     *
     * @param transformerAnimAdd transformerAnimAdd
     */
    public void setTransformerAnimAdd(AnimationTransformer transformerAnimAdd) {
        mAnimationHelper.setTransformerAnimAdd(transformerAnimAdd);
    }

    /**
     * set Transformer Anim Remove
     *
     * @param transformerAnimRemove transformerAnimRemove
     */
    public void setTransformerAnimRemove(AnimationTransformer transformerAnimRemove) {
        mAnimationHelper.setTransformerAnimRemove(transformerAnimRemove);
    }

    /**
     * set Anim Add Remove Interpolator
     *
     * @param animAddRemoveInterpolator animAddRemoveInterpolator
     */
    public void setAnimAddRemoveInterpolator(Interpolator animAddRemoveInterpolator) {
        mAnimationHelper.setAnimAddRemoveInterpolator(animAddRemoveInterpolator);
    }

    /**
     * set CardSize Ratio
     *
     * @param cardSizeRatio cardSizeRatio
     */
    public void setCardSizeRatio(float cardSizeRatio) {
        this.mCardRatio = cardSizeRatio;
        setCardSize(false);
    }

    /**
     * boolean isAnimating
     *
     * @return isAnimating
     */
    public boolean isAnimating() {
        return mAnimationHelper.isAnimating();
    }

    /**
     * set view setCardAnimationListener
     *
     * @param cardAnimationListener cardAnimationListener
     */
    public void setCardAnimationListener(CardAnimationListener cardAnimationListener) {
        mAnimationHelper.setCardAnimationListener(cardAnimationListener);
    }

    /**
     * Card动画开始和结束的监听接口
     */
    public static interface CardAnimationListener {
        void onAnimationStart();

        void onAnimationEnd();
    }
}
